#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct fraction
{
    int numerator;
    int denominator;
};

struct car
{
    char *name;
    float price;
    int speed;
};

// 声明结构体book，并声明它的一个变量
struct book
{
    char title[500];
    char author[100];
    float value;
} b1;

// 声明匿名结构体，并声明它的一个变量
struct
{
    char title[500];
    char author[100];
    float value;
} b2;

// 声明结构体时声明变量，并初始化
struct
{
    char title[500];
    char author[100];
    float value;
} b3 = {"Harry Potter", "J. K. Rowling", 10.0},
  b4 = {"Cancer Ward", "Aleksandr Solzhenitsyn", 7.85};

// 声明结构体cell_phone, 并为其取了一个别名phone
typedef struct cell_phone
{
    int cell_no;
    float minutes_of_charge;
} phone;

struct it_book
{
    char title[500];
    char author[100];
    float value;
} *it_b1;

struct s1
{
    char a;
    int b;
    char c;
};
struct s2
{
    char a;
    char b;
    int c;
};
struct s3
{
    int c;
    char a;
    char b;
};

struct s4
{
    struct s1 s;
    char a;
    char b;
    int c;
};

struct cat
{
    char name[30];
    short age;
};

struct turtle
{
    char *name;
    char *species;
    int age;
};

void happy(struct turtle *t)
{
    // (*t).age这样的写法很麻烦。C 语言就引入了一个新的箭头运算符（->），可以从 struct 指针上直接获取属性，大大增强了代码的可读性。
    t->age = t->age + 1;
    // 等同于(*t).age = (*t).age + 1;
};

struct species
{
    char *name;
    int kinds;
};

struct fish
{
    char *name;
    int age;
    struct species breed;
};

struct name
{
    char first[50];
    char last[50];
};

struct student
{
    struct name name;
    short age;
    char sex;
} student1;

struct node
{
    int data;
    struct node *next;
};

struct
{
    unsigned int ab : 1;
    unsigned int cd : 1;
    unsigned int ef : 1;
    unsigned int gh : 1;
} synth;

int main(void)
{
    struct fraction f1;
    f1.numerator = 23;
    f1.denominator = 7;

    printf("%p\t%p\t%p\n", &f1, &f1.numerator, &f1.denominator); // 0x7ffeed555738  0x7ffeed555738  0x7ffeed55573c

    printf("%d\t%d\n", f1.numerator, f1.denominator);

    // 变量saturn是struct car类型，大括号里面同时对它的三个属性赋值。如果大括号里面的值的数量，少于属性的数量，那么缺失的属性自动初始化为0。
    struct car saturn = {"Saturn SL/2", 16000.99, 175};
    printf("%s\t%f\t%d\n", saturn.name, saturn.price, saturn.speed);

    struct car saturn2 = {.speed = 175, .name = "Saturn SL/2", .price = 16000.99};

    strncpy(b1.title, "C Programming Language", sizeof(b1.title) - 1);
    strncpy(b1.author, "Freewater", sizeof(b1.author) - 1);
    b1.value = 110.2;
    printf("%s\t%s\t%f\n", b1.title, b1.author, b1.value);

    phone p = {5551234, 5};

    struct fraction numbers[] = {{23, 7}, {11, 2}};

    // 数组内存空间对齐
    // 为什么浪费这么多空间进行内存对齐呢？这是为了加快读写速度，把内存占用划分成等长的区块，就可以快速在 Struct 结构体中定位到每个属性的起始地址。
    // 为什么：https://blog.csdn.net/Misszhoudandan/article/details/81570548
    // 详解：https://cloud.tencent.com/developer/article/2336993?areaId=106001
    // 结构体对齐规则：
    // 1.1 第一个成员的地址在结构体变量偏移量为0的地址处。
    // 1.2 其中对齐数=编译器默认的一个对齐数与该成员大小的较小值。（vs默认为8, linux中默认为4）
    // 1.3 其他成员变量依次要按照对齐数的整数倍的地址处来存放。
    // 1.4 结构体总体的大小要为最大对齐数的整倍数。（每一个成员变量都有自己的对齐数，与1.3描述的对象不一样）
    // 1.5 如果一个结构体里面包含一个结构体，把其看作一个成员就行（但其整体对齐数不能看作一个对齐数来比是否为最大对齐数）。

    // offsetof 可计算偏移量大小。
    // size_t offsetof(struct name, nember name);
    // （成员相当于类型的偏移量）

    struct s1 q = {0};
    struct s2 w = {0};
    struct s3 e = {0};
    printf("%zd\n", sizeof(q)); // 12
    printf("%zd\n", sizeof(w)); // 8
    printf("%zd\n", sizeof(e)); // 8

    printf("%zd\n", sizeof(struct s4)); // 20

    // 结构体的复制
    // struct 变量可以使用赋值运算符（=），复制给另一个变量，这时会生成一个全新的副本。系统会分配一块新的内存空间，大小与原来的变量相同，把每个属性都复制过去，即原样生成了一份数据。这一点跟数组的复制不一样，务必小心。
    // 总结一下，赋值运算符（=）可以将 struct 结构每个属性的值，一模一样复制一份，拷贝给另一个 struct 变量。这一点跟数组完全不同，使用赋值运算符复制数组，不会复制数据，只会共享地址。
    // 注意，这种赋值要求两个变量是同一个类型，不同类型的 struct 变量无法互相赋值。
    // 另外，C 语言没有提供比较两个自定义数据结构是否相等的方法，无法用比较运算符（比如==和!=）比较两个数据结构是否相等或不等。
    struct cat cat_a, cat_b;
    strcpy(cat_a.name, "Hula");
    cat_a.age = 3;

    cat_b = cat_a;
    cat_b.name[0] = 'M';

    printf("%s\n", cat_a.name); // Hula
    printf("%s\n", cat_b.name); // Mula

    // struct 指针
    struct turtle t1 = {"jacky", "c++,java", 30};
    happy(&t1);
    printf("%d\n", t1.age);

    // struct 嵌套
    // 写法一
    struct fish shark = {"shark", 9, {"Selachimorpha", 500}};

    // 写法二
    struct species myBreed = {"Selachimorpha", 500};
    struct fish shark2 = {"shark", 9, myBreed};

    // 写法三
    struct fish shark3 = {
        .name = "shark",
        .age = 9,
        .breed = {"Selachimorpha", 500}};

    // 写法四
    struct fish shark4 = {
        .name = "shark",
        .age = 9,
        .breed.name = "Selachimorpha",
        .breed.kinds = 500};

    printf("Shark's species is %s\n", shark.breed.name);

    strcpy(student1.name.first, "Harry");
    strcpy(student1.name.last, "Potter");

    // 或者
    struct name myname = {"Harry", "Potter"};
    student1.name = myname;

    struct node *head;

    // 生成一个三个节点的列表 (11)->(22)->(33)
    head = malloc(sizeof(struct node));

    head->data = 11;
    head->next = malloc(sizeof(struct node));

    head->next->data = 22;
    head->next->next = malloc(sizeof(struct node));

    head->next->next->data = 33;
    head->next->next->next = NULL;

    // 遍历这个列表
    for (struct node *cur = head; cur != NULL; cur = cur->next)
    {
        printf("%d ", cur->data);
    }
    printf("\n");
    // 释放内存
    free(head->next->next);
    free(head->next);
    free(head);

    // 位字段
    // 详解：https://zhuanlan.zhihu.com/p/614163404
    synth.ab = 0;
    synth.cd = 1;
    printf("%zd\n", sizeof(synth)); // 4

    // 上面示例中，每个属性后面的:1，表示指定这些属性只占用一个二进制位，所以这个数据结构一共是4个二进制位。
    // 注意，定义二进制位时，结构内部的各个属性只能是整数类型。
    // 实际存储的时候，C 语言会按照int类型占用的字节数，存储一个位字段结构。如果有剩余的二进制位，可以使用未命名属性，填满那些位。也可以使用宽度为0的属性，表示占满当前字节剩余的二进制位，迫使下一个属性存储在下一个字节。
    struct
    {
        unsigned int field1 : 1;
        unsigned int : 2;
        unsigned int field2 : 1;
        unsigned int : 0;
        unsigned int field3 : 1;
    } stuff;
    // 上面示例中，stuff.field1与stuff.field2之间，有一个宽度为两个二进制位的未命名属性。stuff.field3将存储在下一个字节。
    printf("%zd\n", sizeof(stuff)); // 8

    // 弹性数组成员
    // 很多时候，不能事先确定数组到底有多少个成员。如果声明数组的时候，事先给出一个很大的成员数，就会很浪费空间。C 语言提供了一个解决方法，叫做弹性数组成员（flexible array member）。
    struct vstring
    {
        int len;
        char chars[];
    };
    // 上面示例中，struct vstring结构有两个属性。len属性用来记录数组chars的长度，chars属性是一个数组，但是没有给出成员数量。
    // chars数组到底有多少个成员，可以在为vstring分配内存时确定。
    int n = 100;
    struct vstring *str = malloc(sizeof(struct vstring) + n * sizeof(char));
    str->len = n;

    // 上面示例中，假定chars数组的成员数量是n，只有在运行时才能知道n到底是多少。然后，就为struct vstring分配它需要的内存：它本身占用的内存长度，再加上n个数组成员占用的内存长度。最后，len属性记录一下n是多少。
    // 这样就可以让数组chars有n个成员，不用事先确定，可以跟运行时的需要保持一致。
    // 弹性数组成员有一些专门的规则。首先，弹性成员的数组，必须是 struct 结构的最后一个属性。另外，除了弹性数组成员，struct 结构必须至少还有一个其他属性。
    strncpy(str->chars, "hello", n * sizeof(char) - 1);
    printf("%s\t%d\n", str->chars, str->len);
    free(str);
}